package com.gitee.wsl.ext.coroutines

import com.gitee.wsl.coroutines.LinkedSupervisorScope
import com.gitee.wsl.coroutines.strategy.safely.runCatchingSafely
import com.gitee.wsl.concurrent.api.disposable.Disposable
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.currentCoroutineContext
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads

/**
 * Print [Job] children tree.
 */
fun Job.printDebugTree(offset: Int = 0) {
    println(" ".repeat(offset) + this)

    children.forEach {
        it.printDebugTree(offset + 2)
    }

    if (offset == 0) println()
}


suspend fun joinAny(vararg jobs: Job) {
    require(jobs.isNotEmpty())
    select { jobs.forEach { it.onJoin { } } }
}


suspend fun Collection<Job>.joinAny() {
    require(isNotEmpty())
    select { forEach { it.onJoin { } } }
}

/**
 * 将 [Job] "链接"到一个虚拟的 [parent] Job 上。
 *
 * 所谓"链接"，即当 [parent] 关闭或完成时，
 * 会同时关闭当前Job（通过 [Job.invokeOnCompletion]）。
 *
 * [parent] 不会被作为真正的父Job，[Job] 可以"链接"多个虚拟的父Job。
 *
 * @param cancelChecker 关闭当前Job前的检查。只有当 [cancelChecker] 返回 `true` 才会真正的执行 [thisJob.cancel][Job.cancel]。
 * 默认情况下 [cancelChecker] 会使用 [{ !thisJob.isCompleted }][Job.isCompleted]
 *
 */
@JvmName("linkTo")
@JvmOverloads
inline fun Job.linkTo(
    parent: Job,
    crossinline cancelChecker: (cause: Throwable?) -> Boolean = { !this.isCompleted },
): DisposableHandle {
    val thisJob = this
    return parent.invokeOnCompletion { cause ->
        if (cancelChecker(cause)) {
            thisJob.cancel(
                LinkedParentJobCancellationException(
                    "Linked virtual parent Job $parent is completed.",
                    cause
                )
            )
        }
    }
}


/**
 * @see linkTo
 */
class LinkedParentJobCancellationException(override val message: String?, override val cause: Throwable?) :
    CancellationException(message)


/**
 * Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins
 * and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
 *
 * @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
 */
suspend fun Iterable<Job>.joinFirst(
    scope: CoroutineScope,
    cancelOthers: Boolean = true
): Job {
    val resultDeferred = CompletableDeferred<Job>()
    val scope = scope.LinkedSupervisorScope()
    forEach {
        scope.launch {
            it.join()
            resultDeferred.complete(it)
            scope.cancel()
        }
    }
    return resultDeferred.await().also {
        if (cancelOthers) {
            forEach {
                runCatchingSafely { it.cancel() }
            }
        }
    }
}
/**
 * Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins
 * and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
 *
 * Creates [CoroutineScope] using [coroutineContext] for internal purposes
 */
suspend fun Iterable<Job>.joinFirst(
    cancelOthers: Boolean = true
): Job = joinFirst(CoroutineScope(currentCoroutineContext()), cancelOthers)

/**
 * Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins
 * and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
 *
 * @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
 */
suspend fun joinFirst(
    vararg jobs: Job,
    scope: CoroutineScope,
    cancelOthers: Boolean = true
): Job = jobs.toList().joinFirst(scope, cancelOthers)

/**
 * Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins
 * and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
 *
 * Creates [CoroutineScope] using [coroutineContext] for internal purposes
 */
suspend fun joinFirst(
    vararg jobs: Job,
    cancelOthers: Boolean = true
): Job = joinFirst(*jobs, scope = CoroutineScope(currentCoroutineContext()), cancelOthers = cancelOthers)


fun Job.asDisposable(): Disposable =
    object : Disposable {
        override val isDisposed: Boolean get() = !isActive

        override fun dispose() {
            cancel()
        }
    }